package com.sp.business.normalization.impl;

import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sp.generated.xml.firemon.AddressTranslationMethods;
import com.sp.generated.xml.firemon.BooleanProperty;
import com.sp.generated.xml.firemon.IntegerProperty;
import com.sp.generated.xml.firemon.Interface;
import com.sp.generated.xml.firemon.InterfaceReference;
import com.sp.generated.xml.firemon.InterfaceReferenceCollection;
import com.sp.generated.xml.firemon.Member;
import com.sp.generated.xml.firemon.NetworkObject;
import com.sp.generated.xml.firemon.NetworkObjectTypes;
import com.sp.generated.xml.firemon.NormalizedDataSchema;
import com.sp.generated.xml.firemon.PolicyApplication;
import com.sp.generated.xml.firemon.PolicyApplicationTypes;
import com.sp.generated.xml.firemon.RoutingInstance;
import com.sp.generated.xml.firemon.RoutingInstanceReference;
import com.sp.generated.xml.firemon.RoutingInstanceReferenceCollection;
import com.sp.generated.xml.firemon.ServiceObject;
import com.sp.generated.xml.firemon.ServiceObjectTypes;
import com.sp.generated.xml.firemon.StringProperty;
import com.sp.generated.xml.firemon.TranslatedColumn;
import com.sp.generated.xml.firemon.User;
import com.sp.generated.xml.firemon.UserTypes;
import com.sp.generated.xml.firemon.Zone;
import com.sp.generated.xml.firemon.ZoneReference;
import com.sp.generated.xml.firemon.ZoneReferenceCollection;

public abstract class KeyGeneratorBase implements IKeyGenerator
{
  private static final Logger logger = LoggerFactory.getLogger(KeyGeneratorBase.class);
  @Override
  public abstract void generateKeys(NormalizedDataSchema previous, NormalizedDataSchema current);

  public static boolean isExactMatch(StringProperty previous, StringProperty current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    String a = previous.getValue();
    String b = current.getValue();

    if (a == null || b == null)
    {
      return a == null && b == null ? true : false;
    }

    // neither are null, test values
    return a.equals(b) ? true : false;
  }

  /**
   * Find the edit distance between two Strings.
   * 
   * This is the number of changes needed to change one String into another, 
   * where each change is a single character modification (deletion, insertion or substitution).
   * 
   * @param previous
   * @param current
   * @return
   */
  public static int getEditDistance(StringProperty previous, StringProperty current)
  {
    String previousValue = "";
    String currentValue = "";

    if (previous != null && previous.getValue() != null)
      previousValue = previous.getValue();

    if (current != null && current.getValue() != null)
      currentValue = current.getValue();

    return StringUtils.getLevenshteinDistance(previousValue, currentValue);
  }

  public static int getEditDistance(BooleanProperty previous, BooleanProperty current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? 0 : 1;
    }

    // neither are null
    return previous.isValue().equals(current.isValue()) ? 0 : 1;
  }

  public static int getEditDistance(IntegerProperty previous, IntegerProperty current)
  {
    if (previous == null || current == null)
    {
      // both null?
      if (previous == null && current == null)
        return 0;

      return previous == null ? 1 : -1;
    }

    Integer a = previous.getValue();
    Integer b = current.getValue();

    if (a == null || b == null)
    {
      if (a == null && b == null)
        return 0;

      return a == null ? 1 : -1;
    }

    // neither are null, test values, return the absolute value of the difference between the values
    return Math.abs(a - b);
  }

  public static boolean isExactMatch(BooleanProperty previous, BooleanProperty current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    // neither are null, test values
    return previous.isValue().equals(current.isValue()) ? true : false;
  }

  public static boolean isExactMatch(IntegerProperty previous, IntegerProperty current)
  {
    if (previous == null || current == null)
    {
      // both null?
      return previous == null && current == null ? true : false;
      
    }

    if(previous.getValue()==null || current.getValue()==null)
    {
      return (previous.getValue()== current.getValue() ) ? true : false;
    }
    // neither are null, test values
//    if(previous.getValue()==null || current.getValue()==null)
//    {
//      logger.info("error");
//    }
    return previous.getValue().equals(current.getValue()) ? true : false;
  }

  public static boolean isExactMatch(PolicyApplicationTypes previous, PolicyApplicationTypes current)
  {
    if(previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    // neither are null, test values
    return previous.equals(current) ? true : false;
  }
  
  public static boolean isExactMatch(UserTypes previous, UserTypes current)
  {
    if(previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    // neither are null, test values
    return previous.equals(current) ? true : false;
  }

  public static boolean isExactMatch(ServiceObjectTypes previous, ServiceObjectTypes current)
  {
    if(previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }
    
    return previous.equals(current) ? true : false;
  }

  public static boolean isExactMatch(NetworkObjectTypes previous, NetworkObjectTypes current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    return previous.equals(current) ? true : false;
  }

  //member could be interface and networkObject
  //cisco83 could have interface in destination column
  public static boolean isSameNetworkColumn(List<Member> previous, List<Member> current)
  {
    if (previous == null || current == null)
    {
      return ( previous == null && current == null ) ? true : false;
    }

    // neither are null, test values
    if (previous.size() != current.size())
    {
      return false;
    }

    for (Member m : previous)
    {
	  if(m.getObject() instanceof NetworkObject)
	  {
	      NetworkObject prevObj = (NetworkObject) m.getObject();
	      if(prevObj==null)
	      {
	      	return false;
	      }
          if(matchedNetworkObject(prevObj, current))
        	  continue;
          else
        	  return false;
      }
	  else if(m.getObject() instanceof Interface)
	  {
		  Interface prevObj = (Interface) m.getObject();
	      if(prevObj==null)
	      {
	      	return false;
	      }
          if(matchedInterfaceObject(prevObj, current))
        	  continue;
          else
        	  return false;
	  }
    }
     
    return true;
  }
  
  private static boolean matchedNetworkObject(NetworkObject prevNetworkObj, List<Member> current)
  {
	  //skip non NetworkObject type
	  StringProperty name = prevNetworkObj.getName();    
	  boolean matched = false;
      for (Member c : current)
      {
        if(!(c.getObject() instanceof NetworkObject))  
        	continue;
        
        NetworkObject currObj = (NetworkObject) c.getObject();
        if(currObj==null)
        {
        	return false;
        }
        
      //if type is Host then compare ip address directly
        //name 192.168.101.10 SND-EDGEDMZ-EXTERNALVIP description External prod.bankability.mercantile.co.za VIP
        //name 192.168.101.11 SND-EDGEDMZ-INTERNALVIP description internal.mercantile.co.za VIP
        //swap names will cause rules deleted, created or modified
        //the rules should keep the same
        //before
        //access-list ACL_INSIDE line 151 extended permit tcp 196.6.240.0 255.255.248.0 host SND-EDGEDMZ-EXTERNALVIP eq https log informational interval 300 (hitcnt=7892) 0xef0de9a4
        //access-list ACL_INSIDE line 152 extended permit tcp 196.6.240.0 255.255.248.0 host SND-EDGEDMZ-INTERNALVIP eq https log informational interval 300 (hitcnt=7866) 0xd9357a12  

        //after
        //access-list ACL_INSIDE line 151 extended permit tcp 196.6.240.0 255.255.248.0 host SND-EDGEDMZ-INTERNALVIP eq https log informational interval 300 (hitcnt=12153) 0xef0de9a4 
        //access-list ACL_INSIDE line 152 extended permit tcp 196.6.240.0 255.255.248.0 host SND-EDGEDMZ-EXTERNALVIP eq https log informational interval 300 (hitcnt=14853) 0xd
        //notice name swapped but usageIDs are the same
        if(prevNetworkObj.getType() == currObj.getType() && (currObj.getType() == NetworkObjectTypes.HOST ))
        {
        	//only compare ip address when they are populated
        	//FortiNet network object FQDN, ipaddress.getValue is null
        	if(prevNetworkObj.getIPAddress()!= null &&  currObj.getIPAddress() != null)
        	{
        		if(prevNetworkObj.getIPAddress().getValue() != null && currObj.getIPAddress().getValue() !=null )
        		{
        			if(prevNetworkObj.getIPAddress().getValue().equals(currObj.getIPAddress().getValue()))
                	{
                		matched = true;
                        break;
                	}
        		}        		
        	}        	
        }
        
        StringProperty cName = currObj.getName();
        if (isExactMatch(name, cName))
        {
          matched = true;
          break;
        }
      }
      return matched;
  }
  
  private static boolean matchedInterfaceObject(Interface prevObj, List<Member> current)
  {
	  //skip non NetworkObject type
	  StringProperty name = prevObj.getName();    
	  boolean nameMatched = false;
      for (Member c : current)
      {
        if(!(c.getObject() instanceof Interface))  
        	continue;
        
        Interface currObj = (Interface) c.getObject();
        if(currObj==null)
        {
        	return false;
        }
        StringProperty cName = currObj.getName();
        if (isExactMatch(name, cName))
        {
          nameMatched = true;
          break;
        }
      }
      return nameMatched;
  }

  public static boolean isSameServiceColumn(List<Member> previous, List<Member> current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    // neither are null, test values
    if (previous.size() != current.size())
    {
      return false;
    }

    for (Member m : previous)
    {
      ServiceObject prevObj = (ServiceObject) m.getObject();
      //compare name
    	  if(prevObj==null)
    	  {
    		  return false;
    	  }
      StringProperty name = prevObj.getName();
      boolean nameMatched = false;
    	  for(Member c: current)
      {
    		  ServiceObject currObj = (ServiceObject)c.getObject();
    		  if(currObj==null)
    		  {
    			  logger.info("current serviceObject is null prev name " + name.getValue());
    			  return false;
    		  }
        StringProperty cName = currObj.getName();
        if (isExactMatch(name, cName))
        {
          nameMatched = true;
          break;
        }
      }
      if (nameMatched == true)
      {
        continue;
      }
      else
      {
        return false;
      }

    }//for

    return true;
  }

  public static boolean isSameZoneColumn(final List<Member> previous, final List<Member> current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    // neither are null, test values
    if (previous.size() != current.size())
    {
      return false;
    }

    for (Member m : previous)
    {
      Zone prevObj = (Zone) m.getObject();
      //compare name
      if (prevObj == null)
      {
        return false;
      }
      StringProperty name = prevObj.getName();
      boolean nameMatched = false;
      for (Member c : current)
      {
        Zone currObj = (Zone) c.getObject();
        if (currObj == null)
        {
          logger.info("current zone is null prev name " + name.getValue());
          return false;
        }
        StringProperty cName = currObj.getName();
        if (isExactMatch(name, cName))
        {
          nameMatched = true;
          break;
        }
      }
      if (nameMatched == true)
      {
        continue;
      }
      else
      {
        return false;
      }

    }//for

    return true;
  }

  public static boolean isSameUserColumn(final List<Member> previous, final List<Member> current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    // neither are null, test values
    if (previous.size() != current.size())
    {
      return false;
    }

    for (Member m : previous)
    {
      User prevObj = (User) m.getObject();
      //compare name
      if (prevObj == null)
      {
        return false;
      }
      StringProperty name = prevObj.getName();
      boolean nameMatched = false;
      for (Member c : current)
      {
        User currObj = (User) c.getObject();
        if (currObj == null)
        {
          logger.info("current user is null prev name " + name.getValue());
          return false;
        }
        StringProperty cName = currObj.getName();
        if (isExactMatch(name, cName))
        {
          nameMatched = true;
          break;
        }
      }
      if (nameMatched == true)
      {
        continue;
      }
      else
      {
        return false;
      }

    }//for

    return true;
  }

  public static boolean isSamePolicyApplicationColumn(final List<Member> previous, final List<Member> current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    // neither are null, test values
    if (previous.size() != current.size())
    {
      return false;
    }

    for (Member m : previous)
    {
      PolicyApplication prevObj = (PolicyApplication) m.getObject();
      //compare name
      if (prevObj == null)
      {
        return false;
      }
      StringProperty name = prevObj.getName();
      boolean nameMatched = false;
      for (Member c : current)
      {
        PolicyApplication currObj = (PolicyApplication) c.getObject();
        if (currObj == null)
        {
          logger.info("current application is null prev name " + name.getValue());
          return false;
        }
        StringProperty cName = currObj.getName();
        if (isExactMatch(name, cName))
        {
          nameMatched = true;
          break;
        }
      }
      if (nameMatched == true)
      {
        continue;
      }
      else
      {
        return false;
      }

    }//for

    return true;
  }

  public static boolean isSameInterface(final InterfaceReference previous, final InterfaceReference current)
  {
    if (previous == null || current == null)
    {
      return previous == null && current == null ? true : false;
    }

    if (previous.getInterfaceObject() == null || current.getInterfaceObject() == null)
    {
      return previous.getInterfaceObject() == null && current.getInterfaceObject() == null ? true : false;
    }

    final Interface intf1 = (Interface)previous.getInterfaceObject();
    final Interface intf2 = (Interface)current.getInterfaceObject();

    return intf1.getName().getValue().equals(intf2.getName().getValue());
  }
  
  //so far translated column has network object or interface
  //extend this method when new type added
  public static boolean isSameNetworkTranslatedColumn(final TranslatedColumn previous, final TranslatedColumn current)
  {
	  if (previous == null || current == null)
	  {
	      return previous == null && current == null ? true : false;
	  }
	  
	  //if translation method is interface, has to match method, pool static method, we will look into members
	  if( previous.getTranslationMethod().getValue() == AddressTranslationMethods.OUTBOUND_INTERFACE || current.getTranslationMethod().getValue() == AddressTranslationMethods.OUTBOUND_INTERFACE)
	  {
		  return (current.getTranslationMethod().getValue() == previous.getTranslationMethod().getValue() ) ? true : false;
	  }
	  else
	  {
		  //src or dest network object
		  List<Member> prevDstMembers = previous.getMembers().getMembers();
		  List<Member> dstMembers = current.getMembers().getMembers();
		  if (!KeyGeneratorBase.isSameNetworkColumn(prevDstMembers, dstMembers))
		  {
		      return false;
		  }
	  } 
	    
	  return true;
  }
  
  public static boolean isSameServiceTranslatedColumn(final TranslatedColumn previous, final TranslatedColumn current)
  {
	  if (previous == null || current == null)
	  {
	      return previous == null && current == null ? true : false;
	  }	  
	 
	  //service object
	  List<Member> prevDstMembers = previous.getMembers().getMembers();
	  List<Member> dstMembers = current.getMembers().getMembers();
	  if (!KeyGeneratorBase.isSameServiceColumn(prevDstMembers, dstMembers))
	  {
	      return false;
	  }	
	    
	  return true;
  }
  
  //use collection type is better than pass in List
  //it can handle null inside method, instead force calling routine to check null
  public static boolean isSameInterfaceReference(final InterfaceReferenceCollection previous, final InterfaceReferenceCollection current)
  {	    
	  if (previous == null || current == null)
	  {
	    return previous == null && current == null ? true : false;
	  }

	  List<InterfaceReference> prevInRefs = previous.getInterfaceReferences();   
	  List<InterfaceReference> newInRefs = current.getInterfaceReferences();
	  
	    for(InterfaceReference r : prevInRefs)
	    {
	    	boolean matched = false;
	    	for(InterfaceReference c: newInRefs)
	    	{
	          matched = isSameInterface(r,c);
	          if(matched == true)
	        	  break;
	    	}
	    	if(matched == true)
	    		continue;
	    	else
	    		return false;
	    }
	   
	    return true;	
  }

  public static boolean isSameZoneReference(final ZoneReferenceCollection previous, final ZoneReferenceCollection current)
  {
	  if (previous == null || current == null)
	    {
	      return previous == null && current == null ? true : false;
	    }

	  List<ZoneReference> prevZoneRefs = previous.getZoneReferences();
	  List<ZoneReference> newZoneRefs = current.getZoneReferences();

	    for (ZoneReference r : prevZoneRefs)
	    {
	      Zone prevObj = (Zone) r.getZone();
	      //compare name
	      if (prevObj == null)
	      {
	        return false;
	      }
	      StringProperty name = prevObj.getName();
	      boolean nameMatched = false;
	      for (ZoneReference c : newZoneRefs)
	      {
	        Zone currObj = (Zone) c.getZone();
	        if (currObj == null)
	        {
	          logger.info("current zone is null prev name " + name.getValue());
	          return false;
	        }
	        StringProperty cName = currObj.getName();
	        if (isExactMatch(name, cName))
	        {
	          nameMatched = true;
	          break;
	        }
	      }
	      if (nameMatched == true)
	      {
	        continue;
	      }
	      else
	      {
	        return false;
	      }

	    }//for
	  return true;
  }
  
  public static boolean isSameRoutingInstanceReference(final RoutingInstanceReferenceCollection previous, final RoutingInstanceReferenceCollection current)
  {
	  if (previous == null || current == null)
	    {
	      return previous == null && current == null ? true : false;
	    }

	  List<RoutingInstanceReference> prevRoutingInstanceRefs = previous.getRoutingInstanceReferences();
	    List<RoutingInstanceReference> newRoutingInstanceRefs = current.getRoutingInstanceReferences();
	   

	    for (RoutingInstanceReference r : prevRoutingInstanceRefs)
	    {
	      RoutingInstance prevObj = (RoutingInstance) r.getRoutingInstance();
	      //compare name
	      if (prevObj == null)
	      {
	        return false;
	      }
	      StringProperty name = prevObj.getName();
	      boolean nameMatched = false;
	      for (RoutingInstanceReference c : newRoutingInstanceRefs)
	      {
	    	RoutingInstance currObj = (RoutingInstance) c.getRoutingInstance();
	        if (currObj == null)
	        {
	          logger.info("current RoutingInstance is null prev name " + name.getValue());
	          return false;
	        }
	        StringProperty cName = currObj.getName();
	        if (isExactMatch(name, cName))
	        {
	          nameMatched = true;
	          break;
	        }
	      }
	      if (nameMatched == true)
	      {
	        continue;
	      }
	      else
	      {
	        return false;
	      }

	    }//for
	  return true;
  }	   

}
